package com.nulldev.util.web.flexibleKit.scfk.io.streams;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.SocketChannel;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.LongAdder;

import com.nulldev.util.VariableAPI.MathUtil;
import com.nulldev.util.data.Charsets.CharsetUtil;
import com.nulldev.util.io.IOUtils;
import com.nulldev.util.web.HttpServer3.libSocketReader.tlschannel.ClientTlsChannel;

public class Stream implements AutoCloseable {

	public final SocketChannel channel;
	public final ClientTlsChannel tls_channel;
	private final AtomicBoolean shouldBeKeptAlive = new AtomicBoolean();
	protected LongAdder streamReuseCount = new LongAdder();

	public Stream(final SocketChannel channel) throws IOException {
		this.channel = channel;
		this.tls_channel = null;
	}

	public Stream(final SocketChannel raw_channel, final ClientTlsChannel channel) throws IOException {
		this.channel = raw_channel;
		this.tls_channel = channel;
	}

	public int read(final ByteBuffer b) throws IOException {
		return (this.tls_channel != null ? this.tls_channel.read(b) : this.channel.read(b));
	}

	public int read(final byte[] _b, final int off, final int len) throws IOException {
		final ByteBuffer b = ByteBuffer.wrap(_b, off, len);
		return (this.tls_channel != null ? this.tls_channel.read(b) : this.channel.read(b));
	}

	public int write(final byte[] buf) throws IOException {
		final ByteBuffer b = ByteBuffer.wrap(buf);
		return (this.tls_channel != null ? this.tls_channel.write(b) : this.channel.write(b));
	}

	public int write(final byte[] buf, final int off, final int len) throws IOException {
		final ByteBuffer b = ByteBuffer.wrap(buf, off, len);
		return (this.tls_channel != null ? this.tls_channel.write(b) : this.channel.write(b));
	}

	public int writeAll(final String buf) throws IOException {
		return this.writeAll(buf.getBytes(CharsetUtil.UTF_8));
	}

	private static final String LINE_RETURN = "\r\n";

	public int printAll(final String buf) throws IOException {
		return this.writeAll((buf + LINE_RETURN).getBytes(CharsetUtil.UTF_8));
	}

	public int invokeLR() throws IOException {
		return this.writeAll(LINE_RETURN.getBytes(CharsetUtil.UTF_8));
	}

	public int writeAll(final byte[] buf) throws IOException {
		final ByteBuffer b = ByteBuffer.wrap(buf);
		int written = 0;
		while (b.hasRemaining()) {
			written += (this.tls_channel != null ? this.tls_channel.write(b) : this.channel.write(b));
		}
		return written;
	}

	public int writeAll(final byte[] buf, final int off, final int len) throws IOException {
		final ByteBuffer b = ByteBuffer.wrap(buf, off, len);
		int written = 0;
		while (b.hasRemaining()) {
			written += (this.tls_channel != null ? this.tls_channel.write(b) : this.channel.write(b));
		}
		return written;
	}

	public boolean awaitConnection() throws IOException {
//		if (this.tls_channel != null) {
//			if (!this.channel.finishConnect()) return false;
//			else {
//				this.tls_channel.handshake();
//				return true;
//			}
//		}else {
//			
//		}
		if (this.closed.get())
			return false;
		return this.channel.finishConnect();
	}

	private final AtomicBoolean closed = new AtomicBoolean();

	@Override
	public void close() throws IOException {
		this.closed.set(true);
		IOUtils.closeQuietly(this.tls_channel);
		IOUtils.closeQuietly(this.channel);
	}

	public boolean shouldKeepAlive() {
		return this.shouldBeKeptAlive.get() && !this.closed.get();
	}

	public void setKeepAlive(final boolean alive) {
		this.shouldBeKeptAlive.set(alive);
	}

	public ReadableByteChannel channel() {
		return this.tls_channel != null ? this.tls_channel : this.channel;
	}

	public void closeIfNeeded() throws IOException {
		this.free();
		if (!this.shouldBeKeptAlive.get()) {
			this.close();
		}
	}

	private final AtomicBoolean isInUse = new AtomicBoolean();

	public Stream use() {
		if (!this.isInUse.get())
			this.isInUse.set(true);
		this.streamReuseCount.increment();
		return this;
	}

	public long getStreamUseCount() {
		return this.streamReuseCount.sum();
	}

	public long getStreamUseInt() {
		return this.streamReuseCount.intValue();
	}

	public boolean isInUse() {
		return this.isInUse.get();
	}

	public Stream free() {
		if (this.isInUse.get())
			this.isInUse.set(false);
		return this;
	}

	public boolean isClosed() {
		return this.closed.get();
	}

	@Override
	public String toString() {
		return "Stream[hash=" + MathUtil.toHex(super.hashCode()) + ",isClosed=" + this.closed.get() + ",inUse=" + this.isInUse.get() + ",keepAlive="
				+ this.shouldBeKeptAlive.get() + ",isConnected=" + this.channel.isConnected() + ",isOpen=" + this.channel.isOpen() + "]";
	}
}
